#ifndef ODBC_STATEMENT_HPP
#define ODBC_STATEMENT_HPP

#include "odbc/odbc_config.hpp"
#include "odbc/parameter.hpp"
#include "odbc/details/os_sql_wrapper.hpp"
#include "odbc/details/parameter_buffer.hpp"
#include <list>

namespace odbc
{

class connection;
namespace details { class bindable_buffer_base; class statement_visitor; }

/// \ingroup OdbcLibrary
/// \brief It provides the base interface to be used by ODBC statements.
class ODBC_DECL statement
{
	typedef std::list< parameter*> param_collection;

	friend class details::statement_visitor;

public:
	///	\brief It allocates the resources for the statement and initializes the statement string to be executed.
	/// \param conn the connection to be used for statement execution.
	/// \param statementString the statement to be executed.
	statement( const connection& conn, const std::string& statementString);

	/// \brief It deallocates the resources corresponding to the statement.
	virtual ~statement() throw();

	/// \brief It binds a new parameter of type T to the statement.
	/// \param T the type of the parameter.
	/// \param val the value of the parameter.
	/// \param dir the direction of the parameter.
	template< class T>
	parameter& add_parameter( const T& val, parameter::e_direction dir = parameter::in)
	{
		std::auto_ptr< details::parameter_buffer< T> > p_buffer( 
			new details::parameter_buffer<T>( static_cast< SQLSMALLINT>( dir)));
		p_buffer->set_value( val);
		p_buffer->bind( *this, parameters_.size() + 1);
		std::auto_ptr< parameter> p_new_param( new parameter( p_buffer.release()));
		parameters_.push_back( p_new_param.get());
		return *p_new_param.release();
	}

	/// \brief It binds a new parameter of type T to the statement.
	/// \param T the type of the parameter.
	/// \param dir the direction of the parameter.
	/// \details This method allows either adding a NULL input or input/output parameter, or to add an output parameter 
	///		(which is the default behavior).
	template< class T>
	parameter& add_parameter( parameter::e_direction dir = parameter::out)
	{
		std::auto_ptr< details::parameter_buffer< T> > p_buffer( 
			new details::parameter_buffer<T>( static_cast< SQLSMALLINT>( dir)));
		p_buffer->set_value( boost::any());
		p_buffer->bind( *this, parameters_.size() + 1);
		std::auto_ptr< parameter> p_new_param( new parameter( p_buffer.release()));
		parameters_.push_back( p_new_param.get());
		return *p_new_param.release();
	}

	/// \brief It sets the value of the parameter from the specified position.
	/// \param T the type of the parameter
	/// \param index the position of the parameter (zero based).
	/// \param value the value to be set.
	template< class T>
	void set_parameter( std::size_t index, const T& value)
	{
		assert( index < parameters_.size());
		param_collection::iterator param_iter = parameters_.begin();
		std::advance( param_iter, index);
		(*param_iter)->set_value( value);
	}

	/// \brief It sets the value of the parameter from the specified value.
	/// \param index the position of the parameter (zero based).
	/// \param value the value to be set. If empty, the parameter value is set to NULL.
	void set_parameter( std::size_t index, const boost::any& value)
	{
		assert( index < parameters_.size());
		param_collection::iterator param_iter = parameters_.begin();
		std::advance( param_iter, index);
		(*param_iter)->set_value( value);
	}

	/// \brief
	///		It executes the statement.
	/// \details
	///		It prepares the statement, binds the parameters, executes the 
	///		statement and (for safety reasons) it closes the cursor, if any was
	///		created (this statement class doesn't support datasets as results).
	void execute();

protected:
	///	\brief
	///		It prepares the statement for execution.
	/// \exception odbc_error
	///		thrown if an error occurred during statement preparation.
	void prepare_statement();

	///	\brief
	///		It executes the statement.
	/// \exception odbc_error
	///		thrown if an error occurred during binding.
	/// \warning
	///		It assumes that the statement was previously prepared, and that the parameters were bound.
	void execute_statement();

	///	\brief
	///		It closes the cursor opened by the statement execution, if any.
	/// \warning
	///		No error is signaled if the cursor can't be closed, by any reason.
	void close_cursor();

protected:
	///	\brief It contains the statement handle (internal use only).
	SQLHANDLE handle_;

	/// \brief It contains the text of the statement to be executed.
	std::string statementString_;

	param_collection parameters_;

	/// This class is friend of the statement class because it needs access to the handle of the statement for some 
	///	ODBC API function calls. Friendship was preferred instead of making the handle publicly accessible through an 
	///	accessor.
	friend class details::bindable_buffer_base;

	/// The error class is friend of the statement class, because it needs access to the handle of the statement for 
	///	getting diagnosis data. Friendship was preferred instead of making the handle publicly accessible through an 
	///	accessor.
	friend class odbc_error;
};

} // namespace odbc

#endif // ODBC_STATEMENT_HPP
